/** 根据大小切割数组；
 *
 * @gategory Array
 * @param arr 数组
 * @param size 要切割的大小
 * @returns 生成二维数组
 */
export function chunk<T>(arr: T[], size: number = 1) {
  if (size < 1) return [];
  const result: T[][] = [];
  for (let i = 0, len = arr.length; i < len; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

/** 紧凑的
 * 创建一个新数组，包含原数组中所有的非假值元素。
 * 例如false, null,0, "", undefined, 和 NaN 都是被认为是“假值”。
 * @category Array
 * @param arr 待处理的数组
 * @returns  返回过滤掉假值的新数组
 * @example
 * compact([0, 1, false, 2, '', 3])=>[1, 2, 3]
 */
export function compact<T>(arr: T[]) {
  let resIndex = 0;
  const result: T[] = [];
  if (arr == null) {
    return result;
  }
  for (const value of arr) {
    if (value) {
      result[resIndex++] = value;
    }
  }
}

/**
 * 创建一个具有唯一array值的数组，每个值不包含在其他给定的数组中。
 * @category Array
 * @param arr 要检查的数组。
 * @param args 排除的值或数组。
 * @returns 返回一个过滤值后的新数组。
 * difference([3, 2, 1], [4, 2])=> [3, 1];
 */
export function difference(arr: any[], ...args: any[]) {
  const params = args.flat();
  //filter() 不会改变原始数组。
  return arr.filter((i) => !params.includes(i));
}

/**
 * 创建一个按顺序排列的唯一值的数组
 * @category Array
 * @param arr
 * @returns  返回一个新的联合数组。
 */
export function union<T>(...arr: T[]) {
  const set = new Set(arr.flat());
  return Array.from(set);
}

/**
 * 创建一个切片数组，去除array前面的n个元素。（n默认值为1。）
 * @category Array
 * @param arr 数组
 * @param n 删除的元素个数
 * @returns 返回删除后的数组
 * @example
 * drop([1, 2, 3])=> [2, 3]
 */
export function drop<T>(arr: T[], n: number = 1) {
  return arr.slice(n);
}

/**
 * 创建一个切片数组，去除array尾部的n个元素。（n默认值为1。）
 * @category Array
 * @param arr 数组
 * @param n 删除的元素个数
 * @returns 返回删除后的数组
 * @example
 * dropRight([1, 2, 3])=> [1, 2]
 */
export function dropRight(arr: any[], n: number = 1) {
  return arr.slice(0, -n);
}

/**
 * 数组中获得一个随机元素。
 * @category Array
 * @param arr 要取样的数组
 * @returns 返回随机元素
 * @example
 * sample([1, 2, 3, 4])=>2
 */
export function sample<T>(arr: T[]) {
  return arr[Math.floor(Math.random() * arr.length)];
}

/**
 * 洗牌打乱数组元素排序。
 * @category Array
 * @param arr 要取样的数组
 * @returns 返回打乱的数组。
 * @example
 * shuffle([1, 2, 3, 4])=>[4, 1, 3, 2]
 */
export function shuffle<T>(arr: T[]) {
  for (let i = arr.length; i > 0; i--) {
    let j = Math.floor(Math.random() * i);
    [arr[i - 1], arr[j]] = [arr[j], arr[i - 1]];
  }
  return arr;
}

/**
 * 创建一个包含从 start 到 end，但不包含 end 本身范围数字的数组。 如果 start 是负数，而 end 或 step 没有指定，那么 step 从 -1 为开始。 如果 end 没有指定，start 设置为 0。 如果 end 小于 start ，会创建一个空数组，除非指定了 step。

 */
export const range = createRange();
/**
 * 创建一个包含范围start到end（不包括end）的数字的数组。
 * @param fromRight 默认从start->end,反之end->start
 * @returns
 */
export function createRange(fromRight: boolean = false) {
  return (start: number, end?: number, step?: number) => {
    if (end === void 0) {
      end = start;
      start = 0;
    }
    step = step === void 0 ? (start < end ? 1 : -1) : step;
    return baseRange(start, end, step, fromRight);
  };
}
/**
 * 创建一个包含范围start到end（不包括end）的数字的数组。
 * @category Array
 * @param start 开始数值
 * @param end 结束数值
 * @param step 步长
 * @param fromRight 默认从start->end,反之end->start
 * @returns 返回一个包含范围start到end（不包括end）的数字的数组。
 */
function baseRange(
  start: number,
  end: number,
  step: number,
  fromRight: boolean
) {
  let index = -1;
  let len = Math.max(Math.ceil((end - start) / (step || 1)), 0);
  const result = new Array<number>(len);

  while (len--) {
    result[fromRight ? len : ++index] = start;
    start += step;
  }
  return result;
}

/**
 * 带缓存的执行函数
 * @param func
 * @param resolver
 * @returns
 */
export function memoize(func: Function, resolver: Function) {
  const memoized = function (...args: any[]) {
    const key = resolver ? resolver.apply(this, ...args) : args[0];
    if (memoized.cache.has(key)) {
      return memoized.cache.get(key);
    }
    const result = func.apply(this, args);
    memoized.cache.set(key, result);
    return result;
  };
  memoized.cache = new WeakMap();
  return memoized;
}

export function get(
  object: Object,
  path: string | string[],
  defaultValue?: any
) {
  let obj = object,
    p: string[] = [];
  if (typeof path === "string") {
    const reg = /^[^\[\].]/g;
    path = path.match(reg) as string[];
  }
  for (const key of path) {
    if (!obj) {
      return defaultValue;
    }
    obj = obj[key];
  }
  return obj === undefined ? defaultValue : obj;
}

interface IObj {
  [key: string]: any;
}

function countBy(collection: IObj[], iteratee: (item: IObj) => any) {
  const result = {};
  for (const item of collection) {
    const key = iteratee(item);
    result[key] ? result[key]++ : (result[key] = 1);
  }
  return result;
}
